#include "DEHT.h"
#include "generator.h"
#include "misc.h"
#include "Rainbow_Table.h"

int textExportRainbow(DEHT *deht, FILE *file, rainbowTablePref* rainbowInfo, Rule * rule);

/* deht imitating functions for the above func. */
int mygetData(DEHT *ht, DEHT_DISK_PTR dataPtr, const unsigned char *data, int dataMaxAllowedLength);
DEHT_DISK_PTR mygetNextBlock(DEHT *ht,void *block);
int myreadBlock(const DEHT *ht,const DEHT_DISK_PTR blockPtr, void *block);

int main(int argc, char **argv){
	DEHT *deht = NULL;
	rainbowTablePref rainbowHeader;
	Rule *rule = NULL;
	FILE *out1 = NULL, *out2 = NULL;
	char filename1[80] = {0}, filename2[80] = {0};
	char hexBuffer[80] = {0};
	int i = 0;

	rainbowHeader.seedArr = NULL;

	if (argc != 4){
		fprintf(stderr, "Error: Usage text_export_rainbow_table <DEHT files prefix> <out1 filename> <out2 filename>\n");
		return 0;
	}

	deht = load_DEHT_from_files(argv[1], hash1, hash2);
	if (deht == NULL){
		fprintf(stderr, "Error: failed loading DEHT\n");
		return 0;
	}

	if (read_DEHT_pointers_table(deht) == DEHT_STATUS_FAIL){
		lock_DEHT_files(deht);
		fprintf(stderr, "Error: failed loading DEHT\n");
		return 0;
	}

	if (readRainbowDataToDEHT(deht, &rainbowHeader, &rule) == 0){
		lock_DEHT_files(deht);
		fprintf(stderr, "Error: failed reading rainbow table data from DEHT\n");
		return 0;
	}

	strcpy(filename1, argv[2]);
	strcpy(filename2, argv[3]);
	out1 = fopen(filename1, "w");
	out2 = fopen(filename2, "w");

	fprintf(out1, "Seeds:\n");
	for (i = 0; i < rainbowHeader.chainLength-1; i++){
		binary2hexa((unsigned char *) &(rainbowHeader.seedArr[i]), sizeof(LONG_INDEX_PROJ), hexBuffer, 20);
		fprintf(out1, "%s\n", hexBuffer);
	}

	/*write data strings from DEHT to out1 file*/
	fprintf(out1, "\nDATA:\n");
	textExportDEHT(deht, out1);

	fclose(out1);
	textExportRainbow(deht, out2, &rainbowHeader, rule);
	fclose(out2);
	lock_DEHT_files(deht);
	return 1;

}

int textExportRainbow(DEHT *deht, FILE *file, rainbowTablePref* rainbowInfo, Rule * rule){
	int i = 0, j = 0, blockSize = 0;
	DEHT_DISK_PTR b = 0, *data = NULL;
	void *block = NULL;
	unsigned char *key = NULL;
	char dataBuffer[80] = {0};
	/* chain loop defs */
	char hexBuffer[80] = {0};
	BasicHashFunctionPtr h = NULL;
	int hashLength = 0;
	unsigned char *hBuffer = NULL;	
	int insidei = 0;
	LONG_INDEX_PROJ insidek = 0;
	char pswdBuffer[80] = {0}, sk = 0;
	unsigned char tempKey[80] = {0};

	blockSize = deht->header.nPairsPerBlock*(deht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR)) + sizeof(DEHT_DISK_PTR);
	block = calloc(1, blockSize);
	if (block == NULL) {
		fprintf(stderr, "failed to allocate mem. for block object\n");
		return 0;
	}

	parseHashFunction(deht->header.sDictionaryName, &h, &hashLength);
	if (h == NULL){
		free(block);
		fprintf(stderr, "Error: unknown hash function\n");
		return 0;
	}

	hBuffer = (unsigned char *)calloc(hashLength, sizeof(unsigned char));
	if (hBuffer == NULL){
		free(block);
		return 0;
	}


	for (i = 0; i < deht->header.numEntriesInHashTable; i++){

		b = deht->hashTableOfPointersImageInMemory[i];

		while (b != 0){
			if (myreadBlock(deht,b, block) == 0) {
				free(block);
				free(hBuffer);
				fprintf(stderr, "block reading failed\n");
				return 0;
			}
			data = block;
			data--;
			for (j = 0; j < deht->header.nPairsPerBlock; j++){

				key = (unsigned char *)(data + 1);    /*this is the pointer to validation key*/
				keyCpy((unsigned char *)tempKey, (unsigned char *)key, deht->header.nBytesPerValidationKey);
			
				data = (DEHT_DISK_PTR *)(key + deht->header.nBytesPerValidationKey);
				sk = 0;
				for (j = 0; j < deht->header.nBytesPerValidationKey; j++){
					sk = sk | key[j];
				}
				if (sk == 0) break;
				mygetData(deht, *data, (unsigned char *)dataBuffer, 80);
				cryptHash (h, dataBuffer, (unsigned char *)hBuffer);
				binary2hexa(hBuffer, hashLength,hexBuffer, 80);
				fprintf(file, "%s\t%s", (signed char *)dataBuffer, hexBuffer);

				/* chain loop print */
				for (insidei = 0; insidei < rainbowInfo->chainLength-1; insidei++){
					insidek = pseudo_random_function(hBuffer, hashLength,rainbowInfo->seedArr[insidei]);
					generator(insidek, rule, pswdBuffer);
					while (strcmp(pswdBuffer,"") == 0){ /* preventing the empty password */
						insidek++;
						generator(insidek, rule, pswdBuffer);
					}
					cryptHash (h, pswdBuffer, hBuffer);
					binary2hexa(hBuffer, hashLength,hexBuffer, 80);

					fprintf(file, "\t%s\t%s", (signed char *)pswdBuffer, hexBuffer);
				}
				if (keyCmp((unsigned char *)tempKey,(unsigned char *) hBuffer, deht->header.nBytesPerValidationKey) != 0){
					fprintf(stderr, "Error: when begin with %s get wrong chain\n",dataBuffer);
				}
				/* end of chain loop print */
				fprintf(file, "\n");
			}
			if (j < deht->header.nPairsPerBlock-1) break;
			b = mygetNextBlock(deht,block);
		}
	}

	free(block);
	free(hBuffer);
	return 1;
}



/* code duplication from deht  ################################################################################ */
/*things we took from DEHT to make things work here, without breaking the generic properties of DEHT*/

int myreadBlock(const DEHT *ht,const DEHT_DISK_PTR blockPtr, void *block){
	int blockSize = 0;

	blockSize = ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR)) + sizeof(DEHT_DISK_PTR);
	if (ftell(ht->keyFP) != blockPtr)
		fseek(ht->keyFP, blockPtr, 0);

	if (fread(block, blockSize, 1, ht->keyFP) != 1){
		perror(ht->sKeyfileName);
		return 0;
	}
	else
		return 1;
}

int mygetData(DEHT *ht, DEHT_DISK_PTR dataPtr, const unsigned char *data, int dataMaxAllowedLength){
	unsigned char buffer[80] = {0};

	fseek(ht->dataFP, dataPtr, 0);
	/*do the reading but since we don't know how big the string is and where ini the file it is
	 * we can only see that we read something instead of nothing
	 */
	if (fread(buffer, sizeof(unsigned char), dataMaxAllowedLength, ht->dataFP) == 0){
		perror(ht->sDatafileName);
		return 0;
	}
	/*mark the end of the string in the max. allowed size then copy to
	 * the dataBuffer - strcpy will exit on the first '\0' it will see
	 * so we get the first string in the buffer
	 */
	buffer[dataMaxAllowedLength-1] = '\0';
	strcpy((char *)data, (char *)buffer);

	return 1;
}

DEHT_DISK_PTR mygetNextBlock(DEHT *ht,void *block){
	int numPairs = ht->header.nPairsPerBlock;
	int keyBytes = ht->header.nBytesPerValidationKey;
	int ptrSize = sizeof(DEHT_DISK_PTR);

	return *((DEHT_DISK_PTR *)( ((char *)(block)) + numPairs*(keyBytes + ptrSize)));
}
